全局异常过滤器
创建过滤器
nest g f common/filters/all-exception --no-spec
bash
使用 CLI 创建会自动注册到模块,也可手动创建。
过滤器实现
// common/filters/all-exception.filter.ts
import {
Catch, ExceptionFilter, ArgumentsHost,
HttpException, HttpStatus, Logger,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import * as requestIp from 'request-ip';
@Catch()
export class AllExceptionFilter implements ExceptionFilter {
private readonly logger = new Logger(AllExceptionFilter.name);
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
code: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(request),
ip: requestIp.getClientIp(request) || 'unknown',
message: exception instanceof Error ? exception.message : 'Internal server error',
};
this.logger.error(
`${responseBody.path} → ${httpStatus}`,
exception instanceof Error ? exception.stack : undefined,
);
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
typescript
request-ip 包用于解析客户端真实 IP(支持代理头 X-Forwarded-For 等):
pnpm add request-ip
bash
在 main.ts 中全局注册
// main.ts
import { HttpAdapterHost } from '@nestjs/core';
import { AllExceptionFilter } from './common/filters/all-exception.filter';
const app = await NestFactory.create(AppModule);
const httpAdapterHost = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionFilter(httpAdapterHost));
typescript
全局只能注册一个
@Catch()无参的兜底过滤器。如需捕获特定异常类型,可注册多个过滤器。
环境变量驱动全局设置
将全局配置项集中管理到 .env 文件中,通过 ConfigService 读取:
# .env
ERROR_FILTER=true
CORS=false
PREFIX=api
VERSION=1
bash
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bufferLogs: true });
const configService = app.get(ConfigService);
// 全局 Logger
app.useLogger(app.get(WINSTON_MODULE_PROVIDER));
// 全局异常过滤器(可开关)
const errorFilter = configService.get<boolean>('ERROR_FILTER', true);
if (errorFilter) {
const httpAdapterHost = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionFilter(httpAdapterHost));
}
// CORS 跨域(可开关)
const cors = configService.get<boolean>('CORS', false);
if (cors) app.enableCors();
// 全局路由前缀
const prefix = configService.get<string>('PREFIX', 'api');
app.setGlobalPrefix(prefix);
// API 版本控制
const versionString = configService.get<string>('VERSION', '1');
if (versionString) {
let version;
if (versionString.includes(',')) {
version = versionString.split(',').map(v => v.trim());
} else {
version = versionString;
}
app.enableVersioning({ type: VersioningType.URI, defaultVersion: version });
}
const port = configService.get<number>('PORT', 3000);
await app.listen(port);
}
bootstrap();
typescript
| 环境变量 | 默认值 | 说明 |
|---|---|---|
ERROR_FILTER | true | 是否启用全局异常过滤器 |
CORS | false | 是否启用跨域 |
PREFIX | api | 全局路由前缀 |
VERSION | 1 | API 版本号,支持多版本如 1,2 |
API 版本管理
NestJS 支持 4 种版本管理方式:
| 方式 | URL 格式 | 配置 |
|---|---|---|
| URI | /api/v1/users | VersioningType.URI |
| Header | 通过自定义 Header 指定 | VersioningType.HEADER |
| Media Type | 通过 Accept Header | VersioningType.MEDIA_TYPE |
| Custom | 自定义逻辑 | VersioningType.CUSTOM |
URI 版本控制(本节采用)
默认行为——版本号出现在 URL 路径中:
GET /api/v1/users → 版本1
GET /api/v2/users → 版本2
text
在 Controller 或路由级别指定版本:
@Controller('users')
export class UserController {
@Get()
@Version('1')
getHelloV1() {
return 'Hello v1';
}
@Get()
@Version('2')
getHelloV2() {
return 'Hello v2';
}
}
typescript
多版本兼容
在 .env 中设置 VERSION=1,2 即可同时兼容两个版本。未指定 @Version() 的路由默认匹配 defaultVersion 中设置的版本。
VERSION_NEUTRAL 模式
当 .env 中不设置 VERSION 时,使用 VERSION_NEUTRAL 禁用全局版本控制:
import { VERSION_NEUTRAL } from '@nestjs/common';
// 当 versionString 未配置时
app.enableVersioning({
type: VersioningType.URI,
defaultVersion: VERSION_NEUTRAL,
});
typescript
VERSION_NEUTRAL 模式下,未指定版本的路由无需在 URL 中包含版本号(如 /api/users),而显式标注了 @Version('2') 的路由仍需通过 /api/v2/users 访问。
↑